unit DIrr.Triangle3D;

{$I DIrr.inc}

interface

uses
  Math, DIrr.Math, DIrr.Vector3D, DIrr.Line3D, DIrr.Plane3D, DIrr.AabBox3D;

type
  PTriangle3DF = ^TTriangle3DF;
  TTriangle3DF = record
    PointA: TVector3DF;
    PointB: TVector3DF;
    PointC: TVector3DF;
  private
    function IsOnSameSide(const P1, p2, a, b: TVector3DF): Boolean;
  public
    class operator Equal(Value, Other: TTriangle3DF): Boolean;
    class operator NotEqual(Value, Other: TTriangle3DF): Boolean;

		//! Determines if the triangle is totally inside a bounding box.
		(* \param box Box to check.
		\return True if triangle is within the box, otherwise false. *)
    function IsTotalInsideBox(const Box: TAabBox3DF): Boolean;
		//! Determines if the triangle is totally outside a bounding box.
		(* \param box Box to check.
		\return True if triangle is outside the box, otherwise false. *)
    function IsTotalOutsideBox(const Box: TAabBox3DF): Boolean;
		//! Get the closest point on a triangle to a point on the same plane.
		(* \param p Point which must be on the same plane as the triangle.
		\return The closest point of the triangle *)
    function ClosestPointOnTrangle(const p: TVector3DF): TVector3DF;
		//! Check if a point is inside the triangle
		(* \param p Point to test. Assumes that this point is already
		on the plane of the triangle.
		\return True if the point is inside the triangle, otherwise false. *)
    function IsPointInside(const p: TVector3DF): Boolean;
		//! Check if a point is inside the triangle.
		(* This method is an implementation of the example used in a
		paper by Kasper Fauerby original written by Keidy from
		Mr-Gamemaker.
		\param p Point to test. Assumes that this point is already
		on the plane of the triangle.
		\return True if point is inside the triangle, otherwise false. *)
    function IsPointInsideFast(const p: TVector3DF): Boolean;
		//! Get an intersection with a 3d line.
		(* \param line Line to intersect with.
		\param outIntersection Place to store the intersection point, if there is one.
		\return True if there was an intersection, false if not. *)
    function GetIntersectionWithLimitedLine(const Line: TLine3DF;
      var OutIntersection: TVector3DF): Boolean;
		//! Get an intersection with a 3d line.
		(* Please note that also points are returned as intersection which
		are on the line, but not between the start and end point of the line.
		If you want the returned point be between start and end
		use getIntersectionWithLimitedLine().
		\param linePoint Point of the line to intersect with.
		\param lineVect Vector of the line to intersect with.
		\param outIntersection Place to store the intersection point, if there is one.
		\return True if there was an intersection, false if there was not. *)
    function GetIntersectionWithLine(const LinePoint, LineVect: TVector3DF;
      var OutIntersection: TVector3DF): Boolean;
		//! Calculates the intersection between a 3d line and the plane the triangle is on.
		(* \param lineVect Vector of the line to intersect with.
		\param linePoint Point of the line to intersect with.
		\param outIntersection Place to store the intersection point, if there is one.
		\return True if there was an intersection, else false. *)
    function GetIntersectionOfPlaneWithLine(const LinePoint, LineVect: TVector3DF;
      var OutIntersection: TVector3DF): Boolean;
		//! Get the normal of the triangle.
		(* Please note: The normal is not always normalized. *)
    function GetNormal: TVector3DF;
		//! Test if the triangle would be front or backfacing from any point.
		(* Thus, this method assumes a camera position from which the
		triangle is definitely visible when looking at the given direction.
		Do not use this method with points as it will give wrong results!
		\param lookDirection Look direction.
		\return True if the plane is front facing and false if it is backfacing. *)
    function IsFrontFacing(const LookDirection: TVector3DF): Boolean;
    //! Get the plane of this triangle.
    function GetPlane: TPlane3DF;
    //! Get the area of the triangle
    function GetArea: Single;

  end;

implementation



{ TTriangle3DF }

function TTriangle3DF.ClosestPointOnTrangle(
  const p: TVector3DF): TVector3DF;
var
  rab, rbc, rca: TVector3DF;
  l: TLine3DF;
  d1, d2, d3: Single;
begin
  l.StartPos := PointA;
  l.EndPos := PointB;
  rab := l.GetClosestPoint(p);

  l.StartPos := PointB;
  l.EndPos := PointC;
  rbc := l.GetClosestPoint(p);

  l.StartPos := PointC;
  l.EndPos := PointA;
  rca := l.GetClosestPoint(p);

  d1 := rab.GetDistanceFrom(p);
  d2 := rbc.GetDistanceFrom(p);
  d3 := rca.GetDistanceFrom(p);

  if (d1 < d2) then
  begin
    if (d1 < d3) then
    begin
      Result := rab;
      Exit;
    end
    else
    begin
      Result := rca;
      Exit;
    end;
  end;

  if (d2 < d3) then
  begin
    Result := rbc;
    Exit;
  end
  else
  begin
    Result := rca;
    Exit;
  end;
end;

class operator TTriangle3DF.Equal(Value, Other: TTriangle3DF): Boolean;
begin
  Result := (Value.PointA = Other.PointA) and
    (Value.PointB = Other.PointB) and (Value.PointC = Other.PointC);
end;

function TTriangle3DF.GetArea: Single;
begin
  Result := (PointB - PointA).CrossProduct(PointC - PointA).GetLength * 0.5;
end;

function TTriangle3DF.GetIntersectionWithLine(const LinePoint,
  LineVect: TVector3DF; var OutIntersection: TVector3DF): Boolean;
begin
  if GetIntersectionOfPlaneWithLine(LinePoint, LineVect, OutIntersection) then
  begin
    Result := IsPointInside(OutIntersection);
  end
  else
  begin
    Result := False;
  end;
end;

function TTriangle3DF.GetIntersectionOfPlaneWithLine(const LinePoint,
  LineVect: TVector3DF; var OutIntersection: TVector3DF): Boolean;
var
  Normal: TVector3DF;
  t2, d, t: Single;
begin
  Normal := GetNormal;
  Normal.Normalize;
  t2 := Normal.DotProduct(LineVect);
  if IsZero(t2) then
  begin
    Result := False;
    Exit;
  end;

  d := PointA.DotProduct(Normal);
  t := -(Normal.DotProduct(LinePoint) - d) / t2;
  OutIntersection := LinePoint + (LineVect * t);
  Result := True;
end;

function TTriangle3DF.GetIntersectionWithLimitedLine(const Line: TLine3DF;
  var OutIntersection: TVector3DF): Boolean;
begin
  Result := GetIntersectionWithLine(Line.StartPos, Line.GetVector, OutIntersection) and
    OutIntersection.IsBetweenPoints(Line.StartPos, Line.EndPos);
end;

function TTriangle3DF.GetNormal: TVector3DF;
begin
  Result := (PointB - PointA).CrossProduct(PointC - PointA);
end;

function TTriangle3DF.GetPlane: TPlane3DF;
begin
  Result.SetPlane(PointA, PointB, PointC);
end;

function TTriangle3DF.IsFrontFacing(
  const LookDirection: TVector3DF): Boolean;
var
  n: TVector3DF;
  d: Single;
begin
  n := GetNormal;
  n.Normalize;
  d := n.DotProduct(LookDirection);
  Result := (d <= 0.0);
end;

function TTriangle3DF.IsOnSameSide(const P1, p2, a, b: TVector3DF): Boolean;
var
  bminusa, cp1, cp2: TVector3DF;
begin
  bminusa := b - a;
  cp1 := bminusa.CrossProduct(p1 - a);
  cp2 := bminusa.CrossProduct(p2 - a);

  Result := (cp1.DotProduct(cp2) >= 0.0);
end;

function TTriangle3DF.IsPointInside(const p: TVector3DF): Boolean;
begin
  Result := IsOnSameSide(p, PointA, PointB, PointC) and
    IsOnSameSide(p, PointB, PointA, PointC) and
    IsOnSameSide(p, PointC, PointA, PointB);
end;

function TTriangle3DF.IsPointInsideFast(const p: TVector3DF): Boolean;
var
  f, g: TVector3DF;
  a, b, c: Single;
  vp: TVector3DF;
  d, e: Single;
  x, y, z, ac_bb: Single;
begin
  f := PointB - PointA;
  g := PointC - PointA;

  a := f.DotProduct(f);
  b := f.DotProduct(g);
  c := g.DotProduct(g);

  vp := p - PointA;
  d := vp.DotProduct(f);
  e := vp.DotProduct(g);

  x := d * c - e * b;
  y := e * a - d * b;
  ac_bb := a * c - b * b;
  z := x + y - ac_bb;

  // return sign(z) && !(sign(x)||sign(y))
  Result := ((IR(z) and (not (IR(x) or IR(y)))) and $80000000) <> 0;
end;

function TTriangle3DF.IsTotalInsideBox(const Box: TAabBox3DF): Boolean;
begin
  Result := Box.IsPointInside(PointA) and Box.IsPointInside(PointB) and
    Box.IsPointInside(PointC);
end;

function TTriangle3DF.IsTotalOutsideBox(const Box: TAabBox3DF): Boolean;
begin
  with Box do
  begin
    Result := ((PointA.X > MaxEdge.X) and (PointB.X > MaxEdge.X) and (PointC.X > MaxEdge.X)) or
      ((PointA.Y > MaxEdge.Y) and (PointB.Y > MaxEdge.Y) and (PointC.Y > MaxEdge.Y)) or
      ((PointA.Z > MaxEdge.Z) and (PointB.Z > MaxEdge.Z) and (PointC.Z > MaxEdge.Z)) or
      ((PointA.X < MinEdge.X) and (PointB.X < MinEdge.X) and (PointC.X < MinEdge.X)) or
      ((PointA.Y < MinEdge.Y) and (PointB.Y < MinEdge.Y) and (PointC.Y < MinEdge.Y)) or
      ((PointA.Z < MinEdge.Z) and (PointB.Z < MinEdge.Z) and (PointC.Z < MinEdge.Z));
  end;
end;

class operator TTriangle3DF.NotEqual(Value, Other: TTriangle3DF): Boolean;
begin
  Result := not (Value = Other);
end;

end.